<!doctype html>
<!--
  Minimal Mistakes Jekyll Theme 4.19.3 by Michael Rose
  Copyright 2013-2019 Michael Rose - mademistakes.com | @mmistakes
  Free for personal and commercial use under the MIT license
  https://github.com/mmistakes/minimal-mistakes/blob/master/LICENSE
-->
<html lang="zh" class="no-js">
  <head>
    <meta charset="utf-8">

<!-- begin _includes/seo.html --><title>RISC-V from Scratch 2 - 峰子的乐园</title>
<meta name="description" content="RISC-V from Scratch 2：硬件布局，链接器脚本与 C 运行时">


  <meta name="author" content="丁峰 Feng Ding">


<meta property="og:type" content="article">
<meta property="og:locale" content="zh_CN">
<meta property="og:site_name" content="峰子的乐园">
<meta property="og:title" content="RISC-V from Scratch 2">
<meta property="og:url" content="https://dingfen.github.io/risc-v/2020/07/26/riscv-from-scratch-2.html">


  <meta property="og:description" content="RISC-V from Scratch 2：硬件布局，链接器脚本与 C 运行时">



  <meta property="og:image" content="https://dingfen.github.io/assets/img/teaser.jpg">





  <meta property="article:published_time" content="2020-07-26T00:00:00+00:00">





  

  


<link rel="canonical" href="https://dingfen.github.io/risc-v/2020/07/26/riscv-from-scratch-2.html">




<script type="application/ld+json">
  {
    "@context": "https://schema.org",
    
      "@type": "Person",
      "name": "丁峰 (Feng Ding)",
      "url": "https://dingfen.github.io/"
    
  }
</script>






<!-- end _includes/seo.html -->


<link href="/feed.xml" type="application/atom+xml" rel="alternate" title="峰子的乐园 Feed">

<!-- https://t.co/dKP3o1e -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script>
  document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/g, '') + ' js ';
</script>

<!-- For all browsers -->
<link rel="stylesheet" href="/assets/css/main.css">

<!--[if IE]>
  <style>
    /* old IE unsupported flexbox fixes */
    .greedy-nav .site-title {
      padding-right: 3em;
    }
    .greedy-nav button {
      position: absolute;
      top: 0;
      right: 0;
      height: 100%;
    }
  </style>
<![endif]-->



    <!-- start custom head snippets -->

<!-- insert favicons. use https://realfavicongenerator.net/ -->

<!-- end custom head snippets -->

  </head>

  <body class="layout--single categories">
    <nav class="skip-links">
  <h2 class="screen-reader-text">Skip links</h2>
  <ul>
    <li><a href="#site-nav" class="screen-reader-shortcut">Skip to primary navigation</a></li>
    <li><a href="#main" class="screen-reader-shortcut">Skip to content</a></li>
    <li><a href="#footer" class="screen-reader-shortcut">Skip to footer</a></li>
  </ul>
</nav>

    <!--[if lt IE 9]>
<div class="notice--danger align-center" style="margin: 0;">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience.</div>
<![endif]-->

    

<div class="masthead">
  <div class="masthead__inner-wrap">
    <div class="masthead__menu">
      <nav id="site-nav" class="greedy-nav">
        
        <a class="site-title" href="/">
          峰子的乐园
          <span class="site-subtitle">ideas and techs worth learning and spreading</span>
        </a>
        <ul class="visible-links"><li class="masthead__menu-item">
              <a href="/">Home</a>
            </li><li class="masthead__menu-item">
              <a href="/home/about">About</a>
            </li><li class="masthead__menu-item">
              <a href="/home/blog">Blogs</a>
            </li><li class="masthead__menu-item">
              <a href="/categories">Categories</a>
            </li><li class="masthead__menu-item">
              <a href="https://google.com">External Link</a>
            </li></ul>
        
        <button class="search__toggle" type="button">
          <span class="visually-hidden">Toggle search</span>
          <svg class="icon" width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.99 16">
            <path d="M15.5,13.12L13.19,10.8a1.69,1.69,0,0,0-1.28-.55l-0.06-.06A6.5,6.5,0,0,0,5.77,0,6.5,6.5,0,0,0,2.46,11.59a6.47,6.47,0,0,0,7.74.26l0.05,0.05a1.65,1.65,0,0,0,.5,1.24l2.38,2.38A1.68,1.68,0,0,0,15.5,13.12ZM6.4,2A4.41,4.41,0,1,1,2,6.4,4.43,4.43,0,0,1,6.4,2Z" transform="translate(-.01)"></path>
          </svg>
        </button>
        
        <button class="greedy-nav__toggle hidden" type="button">
          <span class="visually-hidden">切换菜单</span>
          <div class="navicon"></div>
        </button>
        <ul class="hidden-links hidden"></ul>
      </nav>
    </div>
  </div>
</div>


    <div class="initial-content">
      
  







<div class="page__hero--overlay"
  style=" background-image: url('/assets/img/teaser.jpg');"
>
  
    <div class="wrapper">
      <h1 id="page-title" class="page__title" itemprop="headline">
        
          RISC-V from Scratch 2

        
      </h1>
      
        <p class="page__lead">RISC-V from Scratch 2：硬件布局，链接器脚本与 C 运行时
</p>
      
      
        <p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> 




  11 minutes read

</p>
      
      
      
    </div>
  
  
</div>





<div id="main" role="main">
  
  <div class="sidebar sticky">
  


<div itemscope itemtype="https://schema.org/Person">

  
    <div class="author__avatar">
      
        <img src="/assets/img/avatar.jpg" alt="丁峰 Feng Ding" itemprop="image">
      
    </div>
  

  <div class="author__content">
    
      <h3 class="author__name" itemprop="name">丁峰 Feng Ding</h3>
    
    
      <div class="author__bio" itemprop="description">
        <p>Programmer, Graduate majored in CS</p>

      </div>
    
  </div>

  <div class="author__urls-wrapper">
    <button class="btn btn--inverse">follow</button>
    <ul class="author__urls social-icons">
      
        <li itemprop="homeLocation" itemscope itemtype="https://schema.org/Place">
          <i class="fas fa-fw fa-map-marker-alt" aria-hidden="true"></i> <span itemprop="name">Hefei, Anhui, China</span>
        </li>
      

      
        
          
            <li><a href="df12138@mail.ustc.edu.cn" rel="nofollow noopener noreferrer"><i class="fas fa-fw fa-envelope-square" aria-hidden="true"></i><span class="label">Email</span></a></li>
          
        
          
            <li><a href="https://github.com/dingfen" rel="nofollow noopener noreferrer"><i class="fab fa-fw fa-github" aria-hidden="true"></i><span class="label">GitHub</span></a></li>
          
        
      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      <!--
  <li>
    <a href="http://link-to-whatever-social-network.com/user/" itemprop="sameAs" rel="nofollow noopener noreferrer">
      <i class="fas fa-fw" aria-hidden="true"></i> Custom Social Profile Link
    </a>
  </li>
-->
    </ul>
  </div>
</div>

  
  </div>



  <article class="page" itemscope itemtype="https://schema.org/CreativeWork">
    <meta itemprop="headline" content="RISC-V from Scratch 2">
    <meta itemprop="description" content="RISC-V from Scratch 2：硬件布局，链接器脚本与 C 运行时">
    <meta itemprop="datePublished" content="2020-07-26T00:00:00+00:00">
    

    <div class="page__inner-wrap">
      

      <section class="page__content" itemprop="text">
        
          <aside class="sidebar__right sticky">
            <nav class="toc">
              <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> 目录</h4></header>
              <ul class="toc__menu">
  <li><a href="#risc-v-from-scratch-2">RISC-V from scratch 2</a>
    <ul>
      <li><a href="#简介">简介</a></li>
      <li><a href="#搭建环境">搭建环境</a></li>
      <li><a href="#天真的方法">天真的方法</a></li>
      <li><a href="#揭开--v-的面纱">揭开 -v 的面纱</a></li>
      <li><a href="#找到我们的堆栈">找到我们的堆栈</a></li>
      <li><a href="#链接起来">链接起来</a></li>
      <li><a href="#前方高能-运行时">前方高能 运行时</a></li>
      <li><a href="#真正开始调试">真正开始调试</a></li>
      <li><a href="#接下来">接下来</a></li>
    </ul>
  </li>
</ul>

            </nav>
          </aside>
        
        <h1 id="risc-v-from-scratch-2">RISC-V from scratch 2</h1>

<p>今天，我们继续翻译 <em>RISC-V from scratch</em> 系列的第二部分，原文<a href="https://twilco.github.io/riscv-from-scratch/2019/04/27/riscv-from-scratch-2.html">链接</a>。<a href="https://github.com/twilco/riscv-from-scratch">这是</a>该系列的 github 库。</p>

<h2 id="简介">简介</h2>

<p>快速回顾，通过 <em>RISC-V from scratch</em> 系列课程，我们将会探索很多与 RISC-V 及其生态相关的底层概念（例如编译、链接、原语运行时、汇编等）。在<a href="https://dingfen.github.io/2020/07/24/riscv-from-scratch-1.html">第一篇博文</a>中，我们简短的讨论一下 RISC-V 以及为什么它很重要，并搭建起 RISC-V 的工具链，最后在 RISC-V 模拟器和 <a href="https://github.com/sifive/freedom-e-sdk">SiFive’s freedom-e-sdk</a> 的帮助下构建并运行一个简单的 C 程序。</p>

<p><code class="language-plaintext highlighter-rouge">Freedom-e-sdk</code> 使我们在仿真或真正的 RISC-V 处理器上编译，调试和运行任何 C 程序变得很简单。不必担心什么链接脚本、编写运行时来设置堆栈，调用main等的运行时。如果你希望快速提高工作效率，那就太好了，但是这些细节正是我们想要学习的东西！</p>

<p>在这篇文章中，我们将摆脱 <code class="language-plaintext highlighter-rouge">freedom-e-sdk</code> 。我们将编写并尝试调试自己的 C 程序，揭示隐藏在 main 后面的秘密，并检查 <code class="language-plaintext highlighter-rouge">qemu</code> 虚拟机的硬件布局。然后，我们将检查和修改链接器脚本，编写自己的 C 运行时以设置并运行我们的程序，最后调用 GDB 并逐步执行程序。</p>

<h2 id="搭建环境"><a href="https://dingfen.github.io/2020/07/24/riscv-from-scratch-1.html#qemu-and-risc-v-toolchain-setup">搭建环境</a></h2>

<p>如果你还未看本系列博客的第一部分，没有安装 <code class="language-plaintext highlighter-rouge">riscv-qemu</code> 和 RISC-V 工具链，那么赶紧点击上面标题的链接，跳转到 <a href="https://twilco.github.io/riscv-from-scratch/2019/03/10/riscv-from-scratch-1.html#qemu-and-risc-v-toolchain-setup">“QEMU and RISC-V toolchain setup”</a> 。</p>

<p>之后，再将博主创建的 github 库下载下来，作为我们的工作点。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:twilco/riscv-from-scratch.git
<span class="c"># or `git clone https://github.com/twilco/riscv-from-scratch.git` to clone</span>
<span class="c"># via HTTPS rather than SSH</span>
<span class="c"># alternatively, if you are a GitHub user, you can fork this repo.</span>
<span class="c"># https://help.github.com/en/articles/fork-a-repo</span>
<span class="nb">cd </span>riscv-from-scratch/work
</code></pre></div></div>

<p>译注：亲测无需下载 github 库也可实现下面的实验。</p>

<h2 id="天真的方法">天真的方法</h2>

<p>好，让我们写一个简单的 C 程序，开始我们的旅途！</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// file: riscv-from-scratch/work/add.c</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>我们想要跑该程序，第一步就是编译它，生成相应的可执行文件。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># -O0 to disable all optimizations. Without this, GCC might optimize </span>
<span class="c"># away our infinite addition since the result 'c' is never used.</span>
<span class="c"># -g to tell GCC to preserve debug info in our executable.</span>
riscv64-unknown-elf-gcc add.c <span class="nt">-O0</span> <span class="nt">-g</span>
</code></pre></div></div>

<p>编译器生成了 <code class="language-plaintext highlighter-rouge">a.out</code> 文件，这是 <code class="language-plaintext highlighter-rouge">gcc</code> 在没有给定生成文件名字的情况下的默认名。现在，我们可以在 <code class="language-plaintext highlighter-rouge">qemu</code> 里面运行它了：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># -machine tells QEMU which among our list of available machines we want to</span>
<span class="c"># run our executable against.  Run qemu-system-riscv64 -machine help to list</span>
<span class="c"># all available machines.</span>
<span class="c"># -m is the amount of memory to allocate to our virtual machine.</span>
<span class="c"># -gdb tcp::1234 tells QEMU to also start a GDB server on localhost:1234 where</span>
<span class="c"># TCP is the means of communication.</span>
<span class="c"># -kernel tells QEMU what we're looking to run, even if our executable isn't </span>
<span class="c"># exactly a "kernel".</span>
qemu-system-riscv64 <span class="nt">-machine</span> virt <span class="nt">-m</span> 128M <span class="nt">-gdb</span> tcp::1234 <span class="nt">-kernel</span> a.out
</code></pre></div></div>

<p>我们选择了 <code class="language-plaintext highlighter-rouge">virt</code> RISC-V 虚拟机，它是 <code class="language-plaintext highlighter-rouge">riscv-qemu</code> <a href="https://github.com/riscv/riscv-qemu/wiki#machines">自带的</a>。</p>

<p>既然我们的程序已经在 QEMU 中运行，并且在主机端口 1234 打开了 TCP 连接，用于连接 GDB ，那么我们在另一个终端，打开 GDB 与之相连吧：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># --tui gives us a (t)extual (ui) for our GDB session.</span>
<span class="c"># While we can start GDB without any arguments, specifying 'a.out' tells GDB </span>
<span class="c"># to load debug symbols from that file for the newly created session.</span>
riscv64-unknown-elf-gdb <span class="nt">--tui</span> a.out
</code></pre></div></div>

<p>进入了 GDB 的界面：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This GDB was configured as <span class="s2">"--host=x86_64-apple-darwin17.7.0 --target=riscv64-unknown-elf"</span><span class="nb">.</span>           │
Type <span class="s2">"show configuration"</span> <span class="k">for </span>configuration details.                                                  │
For bug reporting instructions, please see:                                                           │
&lt;http://www.gnu.org/software/gdb/bugs/&gt;.                                                              │
Find the GDB manual and other documentation resources online at:                                      │
    &lt;http://www.gnu.org/software/gdb/documentation/&gt;.                                                 │
                                                                                                      │
For <span class="nb">help</span>, <span class="nb">type</span> <span class="s2">"help"</span><span class="nb">.</span>                                                                                │
Type <span class="s2">"apropos word"</span> to search <span class="k">for </span>commands related to <span class="s2">"word"</span>...                                       │
Reading symbols from a.out...                                                                         │
<span class="o">(</span>gdb<span class="o">)</span> 
</code></pre></div></div>

<p>当然，我们还需要告诉 GDB 有一个已经在运行的程序在等着它调试，这和平时使用 GDB 调试程序不同，因为现在我们要调试的程序运行在另一个”机器“上。我们需要打开 TCP 连接，并选择相应的端口，使 GDB 与 程序相连：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> target remote :1234                                                                             │
Remote debugging using :1234
</code></pre></div></div>

<p>现在，我们设置断点了</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) b main
Breakpoint 1 at 0x1018e: file add.c, line 2.
(gdb) b 5 # this is the line within the forever-while loop. int c = a + b;
Breakpoint 2 at 0x1019a: file add.c, line 5.
</code></pre></div></div>

<p>最后，让程序继续运行，直到遇见断点。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(gdb) c
        Continuing.
</code></pre></div></div>

<p>很快，你就会发现程序一直卡死在这里，不会遇到我们之前设置的断点。这到底是怎么一回事呢？我们可以先看一下 GDB 给我们提供的信息：</p>

<p><img src="https://twilco.github.io/assets/img/naive_gdb.png" alt="" /></p>

<p>看一下图中的几个红框：</p>

<ol>
  <li>GDB 无法找到源代码，这原本是展示源代码和断点位置的地方</li>
  <li>GDB 不知道现在运行到第几行，并且 PC 值是 0x0。</li>
  <li>圈出来的值全是 0x0000，很明显 GDB 不知道具体断点位置</li>
</ol>

<h2 id="揭开--v-的面纱">揭开 -v 的面纱</h2>

<p>为了探明之前究竟发生了什么，我们必须先了解一下 C 程序到底是（尤其是在我们看不见的地方）怎么工作的。我们的程序都有一个 <code class="language-plaintext highlighter-rouge">main</code> 函数，但是究竟什么是 <code class="language-plaintext highlighter-rouge">main</code> 函数？为什么我们把它叫做 <code class="language-plaintext highlighter-rouge">main</code> 而不是 <code class="language-plaintext highlighter-rouge">origin</code>、<code class="language-plaintext highlighter-rouge">begin</code> 或者 <code class="language-plaintext highlighter-rouge">entry</code>？很多人都知道我们的程序从 <code class="language-plaintext highlighter-rouge">main</code> 开始运行，但究竟是什么魔力使它如此运作？</p>

<p>为了回答这些问题，我们要重新使用 GCC <code class="language-plaintext highlighter-rouge">-v</code> 编译一下之前的程序，<code class="language-plaintext highlighter-rouge">-v</code> 可以帮助我们获取实际操作的详细输出。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In the `riscv-from-scratch/work` directory...</span>
riscv64-unknown-elf-gcc add.c <span class="nt">-O0</span> <span class="nt">-g</span> <span class="nt">-v</span>
</code></pre></div></div>

<p>第一件我们需要明白的事情就是，虽然 GCC 是 “GNU C Compiler” 的缩写，<code class="language-plaintext highlighter-rouge">gcc</code> 还是会默认链接我们的代码，并且汇编它（加<code class="language-plaintext highlighter-rouge">-c</code> 才会告诉 GCC 只进行编译）。那么这和我们之前要探讨的问题有何关系呢？</p>

<p>接下来，我们再细看一下刚刚 GCC 给我们打印出来的信息。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># The actual `gcc -v` command outputs full paths, but those are quite</span>
<span class="c"># long, so pretend these variables exist.</span>
<span class="c"># $RV_GCC_BIN_PATH = /Users/twilcock/usys/riscv/riscv64-unknown-elf-gcc-&lt;date&gt;-&lt;version&gt;/bin/</span>
<span class="c"># $RV_GCC_LIB_PATH = $RV_GCC_BIN_PATH/../lib/gcc/riscv64-unknown-elf/8.2.0</span>

<span class="nv">$RV_GCC_BIN_PATH</span>/../libexec/gcc/riscv64-unknown-elf/8.2.0/collect2 <span class="se">\</span>
  ...truncated... 
  <span class="nv">$RV_GCC_LIB_PATH</span>/../../../../riscv64-unknown-elf/lib/rv64imafdc/lp64d/crt0.o <span class="se">\ </span>
  <span class="nv">$RV_GCC_LIB_PATH</span>/riscv64-unknown-elf/8.2.0/rv64imafdc/lp64d/crtbegin.o <span class="se">\</span>
  <span class="nt">-lgcc</span> <span class="nt">--start-group</span> <span class="nt">-lc</span> <span class="nt">-lgloss</span> <span class="nt">--end-group</span> <span class="nt">-lgcc</span> <span class="se">\ </span>
  <span class="nv">$RV_GCC_LIB_PATH</span>/rv64imafdc/lp64d/crtend.o
  ...truncated...
<span class="nv">COLLECT_GCC_OPTIONS</span><span class="o">=</span><span class="s1">'-O0'</span> <span class="s1">'-g'</span> <span class="s1">'-v'</span> <span class="s1">'-march=rv64imafdc'</span> <span class="s1">'-mabi=lp64d'</span>
</code></pre></div></div>

<p>不得不承认，即使我裁剪了很多信息，这些信息依然太过于复杂。我必须再详细解释一下。在第一行，<code class="language-plaintext highlighter-rouge">gcc</code> 在运行一个名叫 <code class="language-plaintext highlighter-rouge">collect2</code> 的程序，并且把参数比如 <code class="language-plaintext highlighter-rouge">crt0</code>，<code class="language-plaintext highlighter-rouge">crtbegin.o</code> 和 <code class="language-plaintext highlighter-rouge">crtend.o</code>，并设置了 <code class="language-plaintext highlighter-rouge">-lgcc --start-group</code>等一些 flag 。从 <a href="https://gcc.gnu.org/onlinedocs/gccint/Collect2.html">collect2</a> 来看，简而言之，<code class="language-plaintext highlighter-rouge">collect2</code> 在开始阶段将很多初始化函数一个一个地链接起来。</p>

<p>知道了这些后，就可以明白事实上 GCC 是把多个不同的 <code class="language-plaintext highlighter-rouge">crt</code> 文件和我们自己写的代码链接起来，<code class="language-plaintext highlighter-rouge">crt</code>是 “C runtime” 的缩写，你可以<a href="https://stackoverflow.com/a/27786892/2421349">仔细阅读了解一下每个 crt 是用来干嘛的</a>，不过不用担心，我们目前只关注 <code class="language-plaintext highlighter-rouge">crt0</code>这个文件，它有个很重要的作用：</p>

<blockquote>
  <p><em>This object [crt0] is expected to contain the</em> <code class="language-plaintext highlighter-rouge">_start</code> <em>symbol, which takes care of bootstrapping the initial execution of the program.</em></p>

  <p>目标对象 crt0 应该包含 <code class="language-plaintext highlighter-rouge">_start</code> 符号，该符号用于引导程序的初始执行。</p>

</blockquote>

<p>执​​行的这种初始引导还是要取决于所使用的平台，但是通常它包括重要的任务，例如<strong>设置堆栈框架，传递命令行参数以及调用 <code class="language-plaintext highlighter-rouge">main</code></strong>。是的，我们终于回答了本节开头的问题——<code class="language-plaintext highlighter-rouge">_start</code> 调用了我们的 <code class="language-plaintext highlighter-rouge">main</code> 函数！</p>

<h2 id="找到我们的堆栈">找到我们的堆栈</h2>

<p>终于解决了一个问题，但你可能更想知道这和我们最初的目标有什么关系，即能够逐步使用 GDB 来完成简单的 C 程序。在那之前，我们还需要解决另一些问题，首先要解决的问题是 <code class="language-plaintext highlighter-rouge">crt0</code> 设置堆栈的方式。</p>

<p>我们之前看到，<code class="language-plaintext highlighter-rouge">gcc</code> 链接了 <code class="language-plaintext highlighter-rouge">crt0</code> 文件，这个 <code class="language-plaintext highlighter-rouge">crt0</code>被选中，是根据如下几点做出的决策：</p>

<ul>
  <li>目标平台，包括机器、供应商、操作系统，在本文中，指的是 <code class="language-plaintext highlighter-rouge">riscv64-unknown-elf</code></li>
  <li>目标 ISA <code class="language-plaintext highlighter-rouge">rv64imafdc</code></li>
  <li>目标 ABI <code class="language-plaintext highlighter-rouge">lp64d</code></li>
</ul>

<p>之前提到过，<code class="language-plaintext highlighter-rouge">crt0</code> 的一个工作是建立堆栈，但如果我们不知道 CPU 会把哪里当作堆栈，我们还能怎么办呢？确实，神仙来了也办不了，因此，我们需要更多的信息。</p>

<p>回到我们最初开始运行 <code class="language-plaintext highlighter-rouge">qemu</code> 的地方，<code class="language-plaintext highlighter-rouge">qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -kernel a.out</code> 可以看到我们使用了 virt 机器，可喜的是，<code class="language-plaintext highlighter-rouge">qemu</code> 把这个机器的 dump 信息全都给了我们，它放在了 <code class="language-plaintext highlighter-rouge">dtb</code>格式的文件中。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In the `riscv-from-scratch/work` directory...</span>

<span class="c"># Use qemu to dump info about the 'virt' machine in dtb (devicetree blob) </span>
<span class="c"># format.</span>
<span class="c"># The data in this file represents hardware components of a given </span>
<span class="c"># machine / device / board.</span>
qemu-system-riscv64 <span class="nt">-machine</span> virt <span class="nt">-machine</span> <span class="nv">dumpdtb</span><span class="o">=</span>riscv64-virt.dtb
</code></pre></div></div>

<p>然而 <code class="language-plaintext highlighter-rouge">dtb</code> 格式人类是无法轻易看懂的，但有一个工具 <code class="language-plaintext highlighter-rouge">dtc</code> (devicetree compiler) 可以转换成我们可读的内容。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># I'm running MacOS, so I use Homebrew to install this. If you're</span>
<span class="c"># running another OS you may need to do something else.</span>
brew <span class="nb">install </span>dtc
<span class="c"># Convert our .dtb into a human-readable .dts (devicetree source) file.</span>
dtc <span class="nt">-I</span> dtb <span class="nt">-O</span> dts <span class="nt">-o</span> riscv64-virt.dts riscv64-virt.dtb
</code></pre></div></div>

<p>它生成了 <code class="language-plaintext highlighter-rouge">riscv64-virt.dts</code> 文件，里面包含了很多关于 virt 的信息，例如 CPU 核数量，外围设备（例如：UART 挖个小坑）的内存映射地址，以及 RAM 。我们想让我们的堆栈放在合适的位置，那么我们就找到它：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep </span>memory riscv64-virt.dts <span class="nt">-A</span> 3
        memory@80000000 <span class="o">{</span>
                device_type <span class="o">=</span> <span class="s2">"memory"</span><span class="p">;</span>
                reg <span class="o">=</span> &lt;0x00 0x80000000 0x00 0x8000000&gt;<span class="p">;</span>
        <span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<p>可以看到 <code class="language-plaintext highlighter-rouge">device_type</code> 是 “memory” ，而其值，<code class="language-plaintext highlighter-rouge">reg = &lt;...&gt;</code>可以告诉我们想要的，比如内存从哪里开始，有多长。</p>

<p>参考<a href="https://buildmedia.readthedocs.org/media/pdf/devicetree-specification/latest/devicetree-specification.pdf">the devicetree specification</a>，我们看到 reg 的语法是任意数量的 <code class="language-plaintext highlighter-rouge">(base_address，length)</code> 对。但是，reg 内部有四个值——定义一个 memory 不应该只需要两个值吗？</p>

<p>再看一下<a href="https://buildmedia.readthedocs.org/media/pdf/devicetree-specification/latest/devicetree-specification.pdf">the devicetree specification</a>，我了解到，指定地址和长度所需的 &lt;u32&gt; 单元数由节点的父节点（或节点本身）中的 <code class="language-plaintext highlighter-rouge">#address-cells</code> 和 <code class="language-plaintext highlighter-rouge">#size-cells</code> 属性确定。这些值未在我们的内存节点中指定，并且内存节点的父节点只在文件的根部分，让我们在其中查找以下值：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">head</span> <span class="nt">-n8</span> riscv64-virt.dts
/dts-v1/<span class="p">;</span>

/ <span class="o">{</span>
        <span class="c">#address-cells = &lt;0x02&gt;;</span>
        <span class="c">#size-cells = &lt;0x02&gt;;</span>
        compatible <span class="o">=</span> <span class="s2">"riscv-virtio"</span><span class="p">;</span>
        model <span class="o">=</span> <span class="s2">"riscv-virtio,qemu"</span><span class="p">;</span>
</code></pre></div></div>

<p>它用了两个 32-bit 的值来确定一个地址，两个 32-bit 的值确定长度，这意思着， <code class="language-plaintext highlighter-rouge">reg = &lt;0x00 0x80000000 0x00 0x8000000&gt;</code> ，那么我们的内存起始于 <code class="language-plaintext highlighter-rouge">0x00 + 0x80000000</code>，并且长度为 <code class="language-plaintext highlighter-rouge">0x00+0x8000000</code> 字节，意味着它结束于 <code class="language-plaintext highlighter-rouge">0x88000000</code>，更简洁的说法是，始于 <code class="language-plaintext highlighter-rouge">0x80000000</code>的长度为128M 的内存。</p>

<h2 id="链接起来">链接起来</h2>

<p>好，使用 <code class="language-plaintext highlighter-rouge">qemu</code> 和 <code class="language-plaintext highlighter-rouge">dtc</code> ，我们可以成功地找到 RAM 的位置、长度，我们也知道 GCC 会链接默认的 <code class="language-plaintext highlighter-rouge">crt0</code> ，并建起一个不是我们想要的堆栈，那么基于这些信息，我们到底该怎么做，才能得到一个可以运行、调试的程序呢？</p>

<p>好吧，看来默认的 <code class="language-plaintext highlighter-rouge">crt0</code> 并没有完成我们想要的工作，因此我们必须编写自己的 <code class="language-plaintext highlighter-rouge">crt0</code>，然后将其编译，并与我们写的 C 程序链接。我们的 <code class="language-plaintext highlighter-rouge">crt0</code> 需要知道栈顶的起始位置，以进行初始化。虽然不是很推荐，但简便起见，我们在 <code class="language-plaintext highlighter-rouge">crt0</code> 中将此值硬编码为 <code class="language-plaintext highlighter-rouge">0x80000000</code>。这可能会引起不便，例如，当我们想使用具有不同内存属性的其他经过 <code class="language-plaintext highlighter-rouge">qemu</code> 化的 CPU（例如 <code class="language-plaintext highlighter-rouge">sifive_e</code> ）时会发生什么？</p>

<p>好在这个问题还很遥远，且存在一个很好的解决方案。 GNU 的链接程序 <code class="language-plaintext highlighter-rouge">ld</code> 为我们提供了<a href="https://web.archive.org/web/20190525173911/https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/assignments.html">一种定义可以从 <code class="language-plaintext highlighter-rouge">crt0</code> 访问的符号的方法</a>。除了 <code class="language-plaintext highlighter-rouge">ld</code> 提供的外，我们还可以使用它来创建 <code class="language-plaintext highlighter-rouge">__stack_top</code> 符号，它在多个不同的 CPU 之间具有相当的灵活性。</p>

<p>与其从头开始编写我们自己的链接器，不如将 <code class="language-plaintext highlighter-rouge">ld</code> 使用的默认链接器脚本稍加修改，增加我们想要的符号。你可能想知道什么是链接描述文件？<a href="http://www.scoberlin.de/content/media/http/informatik/gcc_docs/ld_3.html">此文</a>总结甚好：</p>
<blockquote>
  <p><em>The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file.</em></p>
</blockquote>

<p>清楚了不？现在我们开始将默认的链接器脚本拷贝下来：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In the `riscv-from-scratch/work` directory...</span>
<span class="c"># Copy the default linker script into riscv64-virt.ld</span>
riscv64-unknown-elf-ld <span class="nt">--verbose</span> <span class="o">&gt;</span> riscv64-virt.ld
</code></pre></div></div>

<p>文件里有很多有意思的信息，包括 <code class="language-plaintext highlighter-rouge">ld</code> 的版本号，可支持的架构等，当然这些东西的存在与否，完全不影响脚本的正常工作，可以将等于号之前的东西全部删掉的。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim riscv64-virt.ld

<span class="c"># Remove everything above and including the ============ line</span>
GNU ld <span class="o">(</span>GNU Binutils<span class="o">)</span> 2.32
  Supported emulations:
   elf64lriscv
   elf32lriscv
using internal linker script:
<span class="o">==================================================</span>
/<span class="k">*</span> Script <span class="k">for</span> <span class="nt">-z</span> combreloc: combine and <span class="nb">sort </span>reloc sections <span class="k">*</span>/
/<span class="k">*</span> Copyright <span class="o">(</span>C<span class="o">)</span> 2014-2019 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted <span class="k">in </span>any medium without royalty provided the copyright
   notice and this notice are preserved.  <span class="k">*</span>/
OUTPUT_FORMAT<span class="o">(</span><span class="s2">"elf64-littleriscv"</span>, <span class="s2">"elf64-littleriscv"</span>,
	      <span class="s2">"elf64-littleriscv"</span><span class="o">)</span>
...rest of the linker script...
</code></pre></div></div>

<p>之后，我们要做的第一件事，就是用 MEMORY 命令告诉 <code class="language-plaintext highlighter-rouge">ld</code> 我们要手动控制内存布局。这为我们能够正确定义 <code class="language-plaintext highlighter-rouge">__stack_top</code> 的位置铺平了道路。然后，找到以 <code class="language-plaintext highlighter-rouge">OUTPUT_ARCH (riscv) </code>开头的行，该行应位于文件顶部，并在其下面添加我们的 MEMORY 命令：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>OUTPUT_ARCH<span class="o">(</span>riscv<span class="o">)</span>
/<span class="k">*</span> <span class="o">&gt;&gt;&gt;</span> Our addition. <span class="o">&lt;&lt;&lt;</span> <span class="k">*</span>/
MEMORY
<span class="o">{</span>
   /<span class="k">*</span> qemu-system-risc64 virt machine <span class="k">*</span>/
   RAM <span class="o">(</span>rwx<span class="o">)</span>  : ORIGIN <span class="o">=</span> 0x80000000, LENGTH <span class="o">=</span> 128M 
<span class="o">}</span>
/<span class="k">*</span> <span class="o">&gt;&gt;&gt;</span> End of our addition. <span class="o">&lt;&lt;&lt;</span> <span class="k">*</span>/
ENTRY<span class="o">(</span>_start<span class="o">)</span>
</code></pre></div></div>

<p>这样，我们就创建了一个叫 RAM 的 memory，权限是 rwx，可读可写可执行。</p>

<p>好的，这样一来，我们定义的内存布局就和 <code class="language-plaintext highlighter-rouge">virt</code>机器完全一致了。但除非我们接着做什么，否则空空一个 RAM 也完全没有用。我们要把自己的堆栈建在 RAM 里面，这就需要定义 <code class="language-plaintext highlighter-rouge">__stack_top</code> 。</p>

<p>这也很简单，打开 <code class="language-plaintext highlighter-rouge">riscv64-virt.ld</code> ，按照以下做即可：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SECTIONS
<span class="o">{</span>
  /<span class="k">*</span> Read-only sections, merged into text segment: <span class="k">*</span>/
  PROVIDE <span class="o">(</span>__executable_start <span class="o">=</span> SEGMENT_START<span class="o">(</span><span class="s2">"text-segment"</span>, 0x10000<span class="o">))</span><span class="p">;</span>
  <span class="nb">.</span> <span class="o">=</span> SEGMENT_START<span class="o">(</span><span class="s2">"text-segment"</span>, 0x10000<span class="o">)</span> + SIZEOF_HEADERS<span class="p">;</span>
  /<span class="k">*</span> <span class="o">&gt;&gt;&gt;</span> Our addition. <span class="o">&lt;&lt;&lt;</span> <span class="k">*</span>/
  PROVIDE<span class="o">(</span>__stack_top <span class="o">=</span> ORIGIN<span class="o">(</span>RAM<span class="o">)</span> + LENGTH<span class="o">(</span>RAM<span class="o">))</span><span class="p">;</span>
  /<span class="k">*</span> <span class="o">&gt;&gt;&gt;</span> End of our addition. <span class="o">&lt;&lt;&lt;</span> <span class="k">*</span>/
  .interp         : <span class="o">{</span> <span class="k">*</span><span class="o">(</span>.interp<span class="o">)</span> <span class="o">}</span>
  .note.gnu.build-id  : <span class="o">{</span> <span class="k">*</span><span class="o">(</span>.note.gnu.build-id<span class="o">)</span> <span class="o">}</span>
</code></pre></div></div>

<p>可以看到，我们使用 <a href="https://web.archive.org/web/20190525173911/https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/assignments.html#PROVIDE">PROVIDE 命令</a>，定义了符号 <code class="language-plaintext highlighter-rouge">__stack_top</code>，它可被任何链接了该脚本的程序访问到，<code class="language-plaintext highlighter-rouge">__stack_top</code>的值是 <code class="language-plaintext highlighter-rouge">ORIGIN(RAM)</code> ，即 <code class="language-plaintext highlighter-rouge">0x80000000</code> 加上 <code class="language-plaintext highlighter-rouge">0x8000000</code>，其位置是 <code class="language-plaintext highlighter-rouge">0x88000000</code></p>

<h2 id="前方高能-运行时">前方高能 运行时</h2>

<p>终于，我们快要完成了。创建一个文件 <code class="language-plaintext highlighter-rouge">crt0.s</code> ，然后加入以下内容：</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.init</span><span class="p">,</span> <span class="s">"ax"</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
    <span class="nf">.cfi_startproc</span>
    <span class="nf">.cfi_undefined</span> <span class="nv">ra</span>
    <span class="nf">.option</span> <span class="nv">push</span>
    <span class="nf">.option</span> <span class="nv">norelax</span>
    <span class="nf">la</span> <span class="nv">gp</span><span class="p">,</span> <span class="nv">__global_pointer$</span>
    <span class="nf">.option</span> <span class="nv">pop</span>
    <span class="nf">la</span> <span class="nb">sp</span><span class="p">,</span> <span class="nv">__stack_top</span>
    <span class="nf">add</span> <span class="nv">s0</span><span class="p">,</span> <span class="nb">sp</span><span class="p">,</span> <span class="nv">zero</span>
    <span class="nf">jal</span> <span class="nv">zero</span><span class="p">,</span> <span class="nv">main</span>
    <span class="nf">.cfi_endproc</span>
<span class="nf">.end</span>
</code></pre></div></div>

<p>值得注意的是，有很多以 . 开头的行。这是一个汇编文件，这是因为，阅读文件的程序是汇编器，在GNU 中它是 <code class="language-plaintext highlighter-rouge">as</code> 文件。以 <code class="language-plaintext highlighter-rouge">.s</code> 开头的行是伪指令，伪指令向汇编程序提供信息，而不是像 RISC-V 汇编指令（例如 <code class="language-plaintext highlighter-rouge">jal</code> 和 <code class="language-plaintext highlighter-rouge">add</code>）那样成为可执行代码。</p>

<p>鉴于汇编语言不是很好读懂，我接下来会给大家一一讲解这程序在干嘛。</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.init</span><span class="p">,</span> <span class="s">"ax"</span>
</code></pre></div></div>

<p>参考 <a href="https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html">GNU as manual</a> 该行旨在告诉编译器接下来的代码会要进入名为 <code class="language-plaintext highlighter-rouge">.init</code> 的 section ，且权限是 <code class="language-plaintext highlighter-rouge">a</code>llocatable and e<code class="language-plaintext highlighter-rouge">x</code>ecutable 。<code class="language-plaintext highlighter-rouge">.init</code> section 也是<a href="https://web.archive.org/web/20190104080351/http://l4u-00.jinr.ru/usoft/WWW/www_debian.org/Documentation/elf/node3.html">通常遵循的惯例</a> ，用于在操作系统内运行代码。可笑的是我们现在还没操作系统（那是因为我们正在写），关于 <code class="language-plaintext highlighter-rouge">.init</code> section ，这个解释更佳：</p>

<blockquote>
  <p>This section holds executable instructions that contribute to the process initialization code. That is, when a program starts to run the system arranges to execute the code in this section before the main program entry point (called <em>main</em> in C programs).</p>
</blockquote>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">.global</code> 是必须的，这是要让 <code class="language-plaintext highlighter-rouge">ld</code> 能看见这个定义的符号，在链接时，<code class="language-plaintext highlighter-rouge">ld</code> 会根据链接器脚本<code class="language-plaintext highlighter-rouge">ENTRY(_start)</code> 寻找 <code class="language-plaintext highlighter-rouge">_start</code> ，找到程序开始执行的地方。</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">_start:</span>
  <span class="nf">.cfi_startproc</span>
  <span class="nf">.cfi_undefined</span> <span class="nv">ra</span>
  <span class="nf">...other</span> <span class="nv">stuff...</span>
  <span class="nf">.cfi_endproc</span>
</code></pre></div></div>

<p>这些 <code class="language-plaintext highlighter-rouge">.cfi</code> 指令会把 frame 结构以及如何展开等信息通知给汇编器、异常展开器等工具。<code class="language-plaintext highlighter-rouge">.cfi_startproc</code> 和 <code class="language-plaintext highlighter-rouge">.cfi_endproc</code> 指示了该函数的开始与结束。<code class="language-plaintext highlighter-rouge">.cfi_undefined ra</code> 告诉<a href="https://sourceware.org/binutils/docs/as/CFI-directives.html">编译器寄存器 <code class="language-plaintext highlighter-rouge">ra</code> 不应当被恢复为以前的值</a> 。因为 <code class="language-plaintext highlighter-rouge">ra</code> 内含的通常是返回地址，其值在第一个开始执行的函数 <code class="language-plaintext highlighter-rouge">_start</code> 前是不确定的。</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.option</span> <span class="nv">push</span>
<span class="nf">.option</span> <span class="nv">norelax</span>
<span class="nf">la</span> <span class="nv">gp</span><span class="p">,</span> <span class="nv">__global_pointer$</span>
<span class="nf">.option</span> <span class="nv">pop</span>
</code></pre></div></div>

<p>这些 <code class="language-plaintext highlighter-rouge">.option</code> 指令可内联汇编代码来修改汇编程序，这在必须使用一组特定的选项汇编指令序列时非常有用。该<a href="https://embarc.org/man-pages/as/RISC_002dV_002dDirectives.html">链接</a>详细说明了为什么这对上面的代码段很重要，因此我将直接引用它（事实上原博主直接抄的那个手册<code class="language-plaintext highlighter-rouge">:)</code>）：</p>

<blockquote>
  <p><em>…since we relax addressing sequences to shorter GP-relative sequences when possible, the initial load of GP must not be relaxed and should be emitted as something like:</em></p>
</blockquote>

<blockquote>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.option</span> <span class="nv">push</span>
<span class="nf">.option</span> <span class="nv">norelax</span>
<span class="nf">la</span> <span class="nv">gp</span><span class="p">,</span> <span class="nv">__global_pointer$</span>
<span class="nf">.option</span> <span class="nv">pop</span>
</code></pre></div>  </div>

  <p><em>in order to produce, after linker relaxation, the expected:</em></p>

  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">auipc</span> <span class="nv">gp</span><span class="p">,</span> <span class="o">%</span><span class="nv">pcrel_hi</span><span class="p">(</span><span class="nv">__global_pointer$</span><span class="p">)</span>
<span class="nf">addi</span> <span class="nv">gp</span><span class="p">,</span> <span class="nv">gp</span><span class="p">,</span> <span class="o">%</span><span class="nv">pcrel_lo</span><span class="p">(</span><span class="nv">__global_pointer$</span><span class="p">)</span>
</code></pre></div>  </div>

  <p><em>instead of just:</em></p>

  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">addi</span> <span class="nv">gp</span><span class="p">,</span> <span class="nv">gp</span><span class="p">,</span> <span class="mi">0</span>
</code></pre></div>  </div>
</blockquote>

<p>最后，看一下这部分代码：</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">_start:</span>
  <span class="nf">...other</span> <span class="nv">stuff...</span>
  <span class="nf">la</span> <span class="nb">sp</span><span class="p">,</span> <span class="nv">__stack_top</span>
  <span class="nf">add</span> <span class="nv">s0</span><span class="p">,</span> <span class="nb">sp</span><span class="p">,</span> <span class="nv">zero</span>
  <span class="nf">jal</span> <span class="nv">zero</span><span class="p">,</span> <span class="nv">main</span>
  <span class="nf">.cfi_endproc</span>
  <span class="nf">.end</span>
</code></pre></div></div>

<p>这里我们终于用到了 <code class="language-plaintext highlighter-rouge">__stack_top</code> 符号，<code class="language-plaintext highlighter-rouge">la</code> 是 RISC-V 的伪汇编指令，意为 “load address” ，它获取 <code class="language-plaintext highlighter-rouge">__stack_top</code> 定义的地址数据，传递给 <code class="language-plaintext highlighter-rouge">sp</code> (stack pointer) 寄存器，这样一来后面的程序就可以使用这个栈了。</p>

<p>接下来，<code class="language-plaintext highlighter-rouge">add s0, sp, zero</code> 就是将 <code class="language-plaintext highlighter-rouge">sp</code> 的值加 0 后存入 <code class="language-plaintext highlighter-rouge">s0</code> ，<code class="language-plaintext highlighter-rouge">s0</code> 在某些方面是一个特殊的寄存器。首先，它是所谓的“保存寄存器”，这意味着它的值可以在函数调用之间保留。其次，<code class="language-plaintext highlighter-rouge">s0</code> 有时用作帧指针(frame pointer)，这使每个函数调用都可以在堆栈上有自己的空间，用于存储传递该函数的参数。函数调用、堆栈指针和帧指针等是一个非常有趣的话题，但是目前，仅知道初始化帧指针 <code class="language-plaintext highlighter-rouge">s0</code> 是我们运行时的重要任务就可以了。</p>

<p>下一句 <code class="language-plaintext highlighter-rouge">jal zero main</code> ，<code class="language-plaintext highlighter-rouge">jal </code>是 “jump and link” 的缩写，其意思是无条件跳转到 <code class="language-plaintext highlighter-rouge">main</code> 符号点。由于 zero 的寄存器 <code class="language-plaintext highlighter-rouge">x0</code> 恒为0，因此该语句除了无条件跳转外无副作用。初次接触 RISC-V 的读者可能会觉得奇怪，为何使用 zero 寄存器作为目标寄存器，来实现一个无条件且无副作用的跳转。为什么要这样做呢……就不能额外加一个明确的无条件跳转指令？</p>

<p>实际上，这是一种巧妙的优化。每多支持一个的指令就意味着更大、更昂贵的处理器，因此 ISA 越简单越好。因而 RISC-V ISA 并不同时支持 <code class="language-plaintext highlighter-rouge">jal</code> 和无条件跳转指令，而是仅要求 <code class="language-plaintext highlighter-rouge">jal</code>，但通过 <code class="language-plaintext highlighter-rouge">jal zero main</code> 来支持无条件跳转。</p>

<p>RISC-V 中有许多类似的优化，其中大多数采用的是伪指令的形式。伪指令是汇编器知道如何转换为其他实际的硬件实现的指令的指令。例如，有一个无条件跳转伪指令 <code class="language-plaintext highlighter-rouge">j offset_address</code>，RISC-V 汇编程序将其转换为 <code class="language-plaintext highlighter-rouge">jal zero，offset_address</code>。有关正式支持的伪指令的完整列表，请在 <a href="https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf">RISC-V规范的v2.2</a>查看。</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">_start:</span>
  <span class="nf">...other</span> <span class="nv">stuff...</span>
  <span class="nf">jal</span> <span class="nv">zero</span><span class="p">,</span> <span class="nv">main</span>
  <span class="nf">.cfi_endproc</span>
  <span class="nf">.end</span>
</code></pre></div></div>

<p>最后一行，仍是一个汇编器指令，<code class="language-plaintext highlighter-rouge">.end</code> 指示了程序文件的结束。</p>

<h2 id="真正开始调试">真正开始调试</h2>

<p>在开始前，回顾一下迄今我们做了什么，我们首先使用 <code class="language-plaintext highlighter-rouge">qemu</code> 和 <code class="language-plaintext highlighter-rouge">dtc</code> 找到了在 <code class="language-plaintext highlighter-rouge">virt</code> 虚拟机中的内存信息，然后使用这些信息，我们开始通过修改 <code class="language-plaintext highlighter-rouge">riscv64-unknown-elf-ld</code> 的链接器脚本，来”手动“控制内存的布局，最后，我们通过使用自定义的符号创建了一个自己的 <code class="language-plaintext highlighter-rouge">crt0.S</code> 文件，创建了栈和全局指针，并最后调用了 <code class="language-plaintext highlighter-rouge">main</code> 函数，好接下来，我们一鼓作气，开始真正的调试工作。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>riscv64-unknown-elf-gcc <span class="nt">-g</span> <span class="nt">-ffreestanding</span> <span class="nt">-O0</span> <span class="nt">-Wl</span>,--gc-sections <span class="se">\</span>
    <span class="nt">-nostartfiles</span> <span class="nt">-nostdlib</span> <span class="nt">-nodefaultlibs</span> <span class="nt">-Wl</span>,-T,riscv64-virt.ld <span class="se">\</span>
    crt0.s add.c
</code></pre></div></div>

<p>使用 gcc 编译、链接。不过这边突然多了一大堆的选项，令人头秃。</p>

<p><code class="language-plaintext highlighter-rouge">-ffreestanding</code> <a href="https://stackoverflow.com/questions/17692428/what-is-ffreestanding-option-in-gcc#17692510">告诉编译器标准库可能不存在</a>，因此不能做任何假设。在主机环境中运行应用程序时，此选项不是必需的，但是我们没有这样做，因为重要的是告诉编译器该信息。</p>

<p><code class="language-plaintext highlighter-rouge">-Wl</code> 是逗号分隔的标志列表，以传递给链接器 <code class="language-plaintext highlighter-rouge">ld</code>。 <code class="language-plaintext highlighter-rouge">--gc-sections</code> 代表“垃圾收集 section”，告诉<code class="language-plaintext highlighter-rouge">ld</code> 在链接后删除未使用的节。 <code class="language-plaintext highlighter-rouge">-nostartfiles</code>，<code class="language-plaintext highlighter-rouge">-nostdlib</code> 和 <code class="language-plaintext highlighter-rouge">-nodefaultlibs</code> 分别告诉链接器不要链接任何标准系统启动文件（例如默认 <code class="language-plaintext highlighter-rouge">crt0</code>），任何标准系统 stdlib 实现或任何标准系统默认可链接库。我们提供了自己的 <code class="language-plaintext highlighter-rouge">crt0</code> 和链接描述文件，因此传递这些标志以告知编译器，我们不希望使用这些默认设置中的任何一个。</p>

<p><code class="language-plaintext highlighter-rouge">-T</code> 允许你将你的链接器脚本路径传给链接器，在我们这次实验中就是 <code class="language-plaintext highlighter-rouge">riscv64-virt.ld</code> 。最后，加上我们想要编译的文件 <code class="language-plaintext highlighter-rouge">crt0.s</code>和 <code class="language-plaintext highlighter-rouge">add.c</code> 。然后，我们得到了 <code class="language-plaintext highlighter-rouge">a.out</code> ，再使用 <code class="language-plaintext highlighter-rouge">qemu</code> 开启虚拟机：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># -S freezes execution of our executable (-kernel) until we explicitly tell </span>
<span class="c"># it to start with a 'continue' or 'c' from our gdb client</span>
qemu-system-riscv64 <span class="nt">-machine</span> virt <span class="nt">-m</span> 128M <span class="nt">-gdb</span> tcp::1234 <span class="nt">-S</span> <span class="nt">-kernel</span> a.out
</code></pre></div></div>

<p>再另开一个终端，打开 <code class="language-plaintext highlighter-rouge">gdb</code> ，装载入 <code class="language-plaintext highlighter-rouge">a.out</code> 的符号表，并链接目标机器：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>riscv64-unknown-elf-gdb <span class="nt">--tui</span> a.out

GNU gdb <span class="o">(</span>GDB<span class="o">)</span> 8.2.90.20190228-git
Copyright <span class="o">(</span>C<span class="o">)</span> 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &lt;http://gnu.org/licenses/gpl.html&gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type <span class="s2">"show copying"</span> and <span class="s2">"show warranty"</span> <span class="k">for </span>details.
This GDB was configured as <span class="s2">"--host=x86_64-apple-darwin17.7.0 --target=riscv64-unknown-elf"</span><span class="nb">.</span>
Type <span class="s2">"show configuration"</span> <span class="k">for </span>configuration details.
For bug reporting instructions, please see:
&lt;http://www.gnu.org/software/gdb/bugs/&gt;.
Find the GDB manual and other documentation resources online at:
    &lt;http://www.gnu.org/software/gdb/documentation/&gt;.

For <span class="nb">help</span>, <span class="nb">type</span> <span class="s2">"help"</span><span class="nb">.</span>
Type <span class="s2">"apropos word"</span> to search <span class="k">for </span>commands related to <span class="s2">"word"</span>...
Reading symbols from a.out...
<span class="o">(</span>gdb<span class="o">)</span>
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> target remote :1234                                                                             │
Remote debugging using :1234
</code></pre></div></div>

<p>设置断点并运行：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>gdb<span class="o">)</span> b main
Breakpoint 1 at 0x8000001e: file add.c, line 2.

<span class="o">(</span>gdb<span class="o">)</span> c
Continuing.

Breakpoint 1, main <span class="o">()</span> at add.c:2
</code></pre></div></div>

<p>啊哈，你会注意到程序真的在你的断点处停下来了，并且 GDB 内部还有很多相关的地址、数据信息，要是想查看寄存器值，使用命令 <code class="language-plaintext highlighter-rouge">info all-registers</code> 就可以了：</p>

<p><img src="https://twilco.github.io/assets/img/working_gdb.png" alt="" /></p>

<h2 id="接下来">接下来</h2>

<p>在我们的下一篇文章中，我们将通过在<code class="language-plaintext highlighter-rouge">virt</code> QEMU 机器上开始实现 <code class="language-plaintext highlighter-rouge">UART</code> 的驱动程序，继续在 RISC-V组装上积累知识。期望了解 UART 是什么以及它如何工作，其他设备树(device tree)属性，实现与NS16550A 兼容的 UART 驱动程序所需的基本构造块等。</p>

        
      </section>

      <footer class="page__meta">
        
        
  


  
  
  

  <p class="page__taxonomy">
    <strong><i class="fas fa-fw fa-tags" aria-hidden="true"></i> tag: </strong>
    <span itemprop="keywords">
    
      
      
      <a href="/tags/#risc-v" class="page__taxonomy-item" rel="tag">RISC-V</a>
    
    </span>
  </p>




  


  
  
  

  <p class="page__taxonomy">
    <strong><i class="fas fa-fw fa-folder-open" aria-hidden="true"></i> category: </strong>
    <span itemprop="keywords">
    
      
      
      <a href="/categories/#risc-v" class="page__taxonomy-item" rel="tag">RISC-V</a>
    
    </span>
  </p>


        
  <p class="page__date"><strong><i class="fas fa-fw fa-calendar-alt" aria-hidden="true"></i> update time:</strong> <time datetime="2020-07-26T00:00:00+00:00">July 26, 2020</time></p>


      </footer>

      <section class="page__share">
  
    <h4 class="page__share-title">share</h4>
  

  <a href="https://twitter.com/intent/tweet?text=RISC-V+from+Scratch+2%20https%3A%2F%2Fdingfen.github.io%2Frisc-v%2F2020%2F07%2F26%2Friscv-from-scratch-2.html" class="btn btn--twitter" onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;" title="share Twitter"><i class="fab fa-fw fa-twitter" aria-hidden="true"></i><span> Twitter</span></a>

  <a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fdingfen.github.io%2Frisc-v%2F2020%2F07%2F26%2Friscv-from-scratch-2.html" class="btn btn--facebook" onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;" title="share Facebook"><i class="fab fa-fw fa-facebook" aria-hidden="true"></i><span> Facebook</span></a>

  <a href="https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fdingfen.github.io%2Frisc-v%2F2020%2F07%2F26%2Friscv-from-scratch-2.html" class="btn btn--linkedin" onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;" title="share LinkedIn"><i class="fab fa-fw fa-linkedin" aria-hidden="true"></i><span> LinkedIn</span></a>
</section>


      
  <nav class="pagination">
    
      <a href="/risc-v/2020/07/24/riscv-from-scratch-1.html" class="pagination--pager" title="RISC-V from Scratch 1
">previous</a>
    
    
      <a href="/2020/07/27/minimal-mistakes.html" class="pagination--pager" title="Minimal Mistakes
">next</a>
    
  </nav>

    </div>

    
  </article>

  
  
    <div class="page__related">
      <h4 class="page__related-title">related</h4>
      <div class="grid__wrapper">
        
          



<div class="grid__item">
  <article class="archive__item" itemscope itemtype="https://schema.org/CreativeWork">
    
      <div class="archive__item-teaser">
        <img src="/assets/img/teaser.jpg" alt="">
      </div>
    
    <h2 class="archive__item-title" itemprop="headline">
      
        <a href="/risc-v/2020/08/17/riscv-from-scratch-6.html" rel="permalink">RISC-V from Scratch 6
</a>
      
    </h2>
    
      <p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> 




  5 minutes read

</p>
    
    <p class="archive__item-excerpt" itemprop="description">RISC-V from Scratch 6：时钟中断
</p>
  </article>
</div>

        
          



<div class="grid__item">
  <article class="archive__item" itemscope itemtype="https://schema.org/CreativeWork">
    
      <div class="archive__item-teaser">
        <img src="/assets/img/teaser.jpg" alt="">
      </div>
    
    <h2 class="archive__item-title" itemprop="headline">
      
        <a href="/risc-v/2020/08/06/riscv-from-scratch-5.html" rel="permalink">RISC-V from Scratch 5
</a>
      
    </h2>
    
      <p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> 




  5 minutes read

</p>
    
    <p class="archive__item-excerpt" itemprop="description">RISC-V from Scratch 5：机器模式
</p>
  </article>
</div>

        
          



<div class="grid__item">
  <article class="archive__item" itemscope itemtype="https://schema.org/CreativeWork">
    
      <div class="archive__item-teaser">
        <img src="/assets/img/teaser.jpg" alt="">
      </div>
    
    <h2 class="archive__item-title" itemprop="headline">
      
        <a href="/risc-v/2020/08/05/riscv-privileged.html" rel="permalink">RISC-V 特权架构
</a>
      
    </h2>
    
      <p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> 




  8 minutes read

</p>
    
    <p class="archive__item-excerpt" itemprop="description">RISC-V 特权架构
</p>
  </article>
</div>

        
          



<div class="grid__item">
  <article class="archive__item" itemscope itemtype="https://schema.org/CreativeWork">
    
      <div class="archive__item-teaser">
        <img src="/assets/img/teaser.jpg" alt="">
      </div>
    
    <h2 class="archive__item-title" itemprop="headline">
      
        <a href="/risc-v/2020/08/01/riscv-from-scratch-4.html" rel="permalink">RISC-V from Scratch 4
</a>
      
    </h2>
    
      <p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> 




  7 minutes read

</p>
    
    <p class="archive__item-excerpt" itemprop="description">RISC-V from Scratch 4：写 UART 驱动（2 / 3）
</p>
  </article>
</div>

        
      </div>
    </div>
  
  
</div>

    </div>

    
      <div class="search-content">
        <div class="search-content__inner-wrap"><form class="search-content__form" onkeydown="return event.key != 'Enter';">
    <label class="sr-only" for="search">
      Enter your search term...
    </label>
    <input type="search" id="search" class="search-input" tabindex="-1" placeholder="输入您要搜索的关键词..." />
  </form>
  <div id="results" class="results"></div></div>

      </div>
    

    <div id="footer" class="page__footer">
      <footer>
        <!-- start custom footer snippets -->

<!-- end custom footer snippets -->
        <div class="page__footer-follow">
  <ul class="social-icons">
    
      <li><strong>follow:</strong></li>
    

    
      
        
          <li><a href="https://github.com/dingfen" rel="nofollow noopener noreferrer"><i class="fab fa-fw fa-github" aria-hidden="true"></i> Github</a></li>
        
      
        
          <li><a href="df12138@mail.ustc.edu.cn" rel="nofollow noopener noreferrer"><i class="fas fa-fw fa-envelope-square" aria-hidden="true"></i> Email</a></li>
        
      
    

    <li><a href="/feed.xml"><i class="fas fa-fw fa-rss-square" aria-hidden="true"></i> Feed</a></li>
  </ul>
</div>

<div class="page__footer-copyright">&copy; 2020 丁峰 (Feng Ding). Powered by <a href="https://jekyllrb.com" rel="nofollow">Jekyll</a> &amp; <a href="https://mademistakes.com/work/minimal-mistakes-jekyll-theme/" rel="nofollow">Minimal Mistakes</a>.</div>

      </footer>
    </div>

    
  <script src="/assets/js/main.min.js"></script>
  <script src="https://kit.fontawesome.com/4eee35f757.js"></script>




<script src="/assets/js/lunr/lunr.min.js"></script>
<script src="/assets/js/lunr/lunr-store.js"></script>
<script src="/assets/js/lunr/lunr-en.js"></script>




    
  <script>
    var disqus_config = function () {
      this.page.url = "https://dingfen.github.io/risc-v/2020/07/26/riscv-from-scratch-2.html";  /* Replace PAGE_URL with your page's canonical URL variable */
      this.page.identifier = "/risc-v/2020/07/26/riscv-from-scratch-2"; /* Replace PAGE_IDENTIFIER with your page's unique identifier variable */
    };
    (function() { /* DON'T EDIT BELOW THIS LINE */
      var d = document, s = d.createElement('script');
      s.src = 'https://https://dingfen.github.io/.disqus.com/embed.js';
      s.setAttribute('data-timestamp', +new Date());
      (d.head || d.body).appendChild(s);
    })();
  </script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>


  





  </body>
</html>
